package ai.wprolog.essentials;

import java.util.Enumeration;
import java.util.Hashtable;

import ai.wprolog.outmsg.Output;

import util.essential.SourceGetter;

/** Parsing library */
public class ParseString {
	private String str;
	private int posn, start, varnum;
	/**
	 * source stores a pointer to the TextField (for the query) or the TextArea
	 * (for the program) so if an error occurs in parsing we can highlight the
	 * correct part of GUI.
	 */
	private Output source;
	/**
	 * The hashtable stores indeces for variables that have been encountered. It
	 * isn't automatically reset since we want to have variables common in a
	 * clause
	 */
	private Hashtable vardict;

	final public String toString() {
		return "{" + str.substring(0, posn) + " ^ " + str.substring(posn)
				+ " | " + vardict + " }";
	}

	/** Initialise variables */
	public ParseString(String s, Output tc) {
		str = s;
		source = tc;
		posn = 0;
		start = 0;
		varnum = 0;
		vardict = new Hashtable();
	}

	/** Get the current character */
	final public char current() {
		if (this.empty())
			return '#'; // can't be space
		else
			return str.charAt(posn);
	}

	/** Is the parsestring empty? */
	final public boolean empty() {
		return posn == (str.length());
	}

	/** Move a character forward */
	final public void advance() {
		posn++;
		if (posn >= str.length())
			posn = str.length();
	}

	// all three get methods must be called before advance.
	/** Recognise a name (sequence of alphanumerics) */
	final public String getname() {
		String s;

		start = posn;
		posn++;
		while (Character.isDigit(this.current())
				| Character.isLowerCase(this.current())
				| Character.isUpperCase(this.current()))
			posn++;
		s = str.substring(start, posn);
		if (posn >= str.length())
			posn = str.length();

		return s;
	}

	/** Recognise a number */
	public String getnum() {
		String s;

		start = posn;
		posn++;
		while (Character.isDigit(this.current()))
			posn++;
		s = str.substring(start, posn);
		if (posn >= str.length())
			posn = str.length();

		return s;
	}

	/**
	 * Get the Term corresponding to a name. If the name is new, then create a
	 * new variable
	 */
	final public Term getvar() {
		String s;
		Term t;
		s = this.getname();
		t = (Term) vardict.get(s);

		if (t == null) {
			t = new Term(varnum++); // XXXX wrong varnum??
			vardict.put(s, t);
		}

		return t;
	}

	/** Handle errors in one place */
	final public void parseerror(String s) throws Exception {
		SourceGetter.out.debug("Unexpected character : " + s);
		source.select(posn, posn + 1);
		throw new Exception();
	}

	/** Skip spaces. Also skips Prolog comments */
	final public void skipspace() throws Exception {
		while (Character.isSpace(this.current()))
			this.advance();
		skipcomment();
	}

	final private void skipcomment() throws Exception {
		if (current() == '%') {
			while (current() != '\n' & current() != '#')
				advance();
			skipspace();
		}
		if (current() == '/') {
			advance();
			if (current() != '*')
				parseerror("expecting ``*''");
			else {
				advance();
				while (current() != '*' & current() != '#')
					advance();
				advance();
				if (current() != '/')
					parseerror("expecting ``/''");
				advance();
			}
			skipspace();
		}
	}

	/** This resets the variable dictionary. */
	final public void nextclause() {
		// create new variables for next clause
		vardict = new Hashtable<String, Term>();
		varnum = 0;
	}

	/**
	 * This takes a hashtable and extends it with the clauses in the string.
	 * 
	 * @param db
	 *            - an initial program, it is augmented with the clauses parsed.
	 * @param s
	 *            - a string representing a Prolog program
	 */
	final public static Hashtable<String,TermList> consult(String s, Hashtable<String,TermList> db,
			Output tc) throws Exception {
		ParseString ps = new ParseString(s, tc);
		String func, prevfunc, index;
		int clausenum, arity, prevarity;
		TermList tls;

		ps.skipspace();
		prevfunc = "";
		prevarity = -1;
		clausenum = 1;

		while (!ps.empty()) {
			tls = new TermList(ps);

			func = tls.term.getfunctor();
			arity = tls.term.getarity();

			if (func.equals(prevfunc) & arity == prevarity)
				clausenum++;
			else {
				clausenum = 1;
				prevfunc = func;
				prevarity = arity;
			}

			index = func + "/" + arity + "-" + clausenum;

			db.put(index, tls);

			ps.skipspace();
			ps.nextclause(); // new set of vars
		}

		return db;
	} // consult

	final public static void resolve(Hashtable<String,TermList> db) {
		// We now resolve all references.
		for (Enumeration<TermList> e = db.elements(); e.hasMoreElements();) {
			((TermList) e.nextElement()).resolve(db);
		}
	}
}